package com.briercliffe.settlers.core.domain;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * User: briercliffe
 * Date: 10/9/13
 */
public class Tile
{
    // ==================================================
    // class static data
    // ==================================================

    // ==================================================
    // class static methods
    // ==================================================

    /**
     * Creates a tile based on a {@link TileType}. Sets up the tile with
     * a port if it is a port tile type.
     *
     * @param iTileType - The tile type to create.
     * @return A tile object with the properties of the TileType.
     */
    public static Tile createFromTileType(final TileType iTileType)
    {
        Tile tile = new Tile(iTileType.getResourceType());
        if (iTileType.isPort())
        {
            tile.placeItem(new Port(iTileType.getPortType()));
        }
        return tile;
    }
    // ==================================================
    // instance data
    // ==================================================
    private ResourceType mResourceType;
    private Placeable mPlacedItem;
    private Map<Edge, Road> mRoads;
    private Map<Vertex, Settlement> mSettlements;
    private AxialCoordinates mAxialCoordinates;

    // ==================================================
    // factories
    // ==================================================

    // ==================================================
    // constructors
    // ==================================================
    public Tile()
    {
        this(ResourceType.BLANK);
    }

    public Tile(final ResourceType iResourceType)
    {
        mResourceType = iResourceType;
        mRoads = new HashMap<>();
        mSettlements = new HashMap<>();
    }
    // ==================================================
    // public methods
    // ==================================================

    /**
     * Check if an item has been placed on this tile.
     *
     * @return True if there is an item, false otherwise.
     */
    public boolean hasItemPlaced()
    {
        return mPlacedItem != null;
    }

    /**
     * Put an item on this tile and return the item that was previously placed, if any.
     * @param iPlacedItem - The item to be placed on this tile.
     * @return The previously placed item, or null
     */
    public Placeable placeItem(final Placeable iPlacedItem)
    {
        Placeable currentItem = mPlacedItem;
        mPlacedItem = iPlacedItem;
        return currentItem;
    }

    /**
     * Test if a road can be placed on a given edge by a given user.
     *
     * @param iEdge - The edge to place the road on.
     * @param iUser - The user attempting to place the road.
     * @return True if the user is allowed to place the road, false otherwise.
     */
    public boolean canPlaceRoad(final Edge iEdge, final User iUser)
    {
        boolean canPlaceRoad = false;

        Collection<Vertex> adjacentVertices = Edge.getAdjacentVertices(iEdge);
        for (Vertex v : adjacentVertices)
        {
            // If there is no settlement, check if there is a road owned by the user
            // on the adjacent edge
            Settlement settlement = mSettlements.get(v);
            if (settlement == null)
            {
                Collection<Edge> adjacentEdges = Edge.getAdjacentEdges(iEdge);
                for (Edge e : adjacentEdges)
                {
                    Road r = mRoads.get(e);
                    if (r != null && r.getOwner() == iUser)
                    {
                        canPlaceRoad = true;
                    }
                }
            }
            else
            {
                // Can only place a road if the user owns the attached settlement
                User owner = settlement.getOwner();
                if (owner != null && owner == iUser)
                {
                    canPlaceRoad = true;
                    break;
                }
            }

        }
        return canPlaceRoad;
    }

    /**
     * Test if a settlement can be place on a given vertex.
     *
     * @param iVertex - The vertex to place the settlement on.
     * @param iUser - The user attempting to place the settlement.
     * @return True if the user is allowed to place the settlement, false otherwise.
     */
    public boolean canPlaceSettlement(final Vertex iVertex, final User iUser)
    {
        // Cannot place a settlement if there is already one on this vertex
        Settlement settlement = mSettlements.get(iVertex);
        if (settlement != null)
        {
            return false;
        }

        List<Vertex> adjacencies = (List<Vertex>) Vertex.getAdjacentVertices(iVertex);
        for (Vertex v : adjacencies)
        {
            // Cannot place a settlement if there is already one
            // on an adjacent vertex
            settlement = mSettlements.get(v);
            if (settlement != null)
            {
                return false;
            }
        }

        return true;
    }

    /**
     * Test if a settlement can be upgraded by the given user.
     *
     * @param iVertex - The vertex with the settlement to be upgraded.
     * @param iUser - The user attempting to upgrade the settlement.
     * @return True if the user is allowed to upgrade the settlement, false otherwise.
     */
    public boolean canUpgradeSettlement(final Vertex iVertex, final User iUser)
    {
        Settlement settlement = mSettlements.get(iVertex);
        if (settlement != null &&
            settlement.getOwner() == iUser &&
            settlement.canUpgrade())
        {
            return true;
        }
        return false;
    }

    /**
     * Place a road for the given user on the given edge. Should be called only
     * if {@link #canPlaceRoad(com.briercliffe.settlers.core.domain.Tile.Edge, User)}
     * returns true.
     *
     * @param iEdge - The edge to place the road on.
     * @param iUser - The user placing the road.
     */
    public void placeRoad(final Edge iEdge, final User iUser)
    {
        Road road = new Road();
        road.setOwner(iUser);
        mRoads.put(iEdge, road);
    }

    /**
     * Places a settlement for a given user on the given vertex. Should be called only
     * if {@link #canPlaceSettlement(com.briercliffe.settlers.core.domain.Tile.Vertex, User)}
     * returns true.
     *
     * @param iVertex - The vertex to place the settlement on.
     * @param iUser - The user placing the settlement.
     */
    public void placeSettlement(final Vertex iVertex, final User iUser)
    {
        Village village = new Village();
        village.setOwner(iUser);
        mSettlements.put(iVertex, village);
    }

    /**
     * Upgrades a settlement on the given vertex. Should be called only if
     * {@link #canUpgradeSettlement(com.briercliffe.settlers.core.domain.Tile.Vertex, User)}
     * returns true.
     *
     * @param iVertex - The vertex with the settlement to upgrade.
     * @param iUser - The user upgrading the settlement.
     */
    public void upgradeSettlement(final Vertex iVertex, final User iUser)
    {
        Settlement settlement = mSettlements.get(iVertex);
        Settlement upgrade = settlement.upgrade();
        mSettlements.put(iVertex, upgrade);
    }
    // ==================================================
    // non public methods
    // ==================================================

    // ==================================================
    // getters / setters
    // ==================================================

    public ResourceType getResourceType()
    {
        return mResourceType;
    }

    public void setResourceType(final ResourceType iResourceType)
    {
        mResourceType = iResourceType;
    }

    public AxialCoordinates getAxialCoordinates()
    {
        return mAxialCoordinates;
    }

    public void setAxialCoordinates(final AxialCoordinates iAxialCoordinates)
    {
        mAxialCoordinates = iAxialCoordinates;
    }

    public Map<Vertex, Settlement> getSettlements()
    {
        return mSettlements;
    }

    public void setSettlements(final Map<Vertex, Settlement> iSettlements)
    {
        mSettlements = iSettlements;
    }

    public Map<Edge, Road> getRoads()
    {
        return mRoads;
    }

    public void setRoads(final Map<Edge, Road> iRoads)
    {
        mRoads = iRoads;
    }

    public Placeable getPlacedItem()
    {
        return mPlacedItem;
    }

    public void setPlacedItem(final Placeable iPlacedItem)
    {
        mPlacedItem = iPlacedItem;
    }

    /**
     * Represents a vertex for a 'pointy-top' hexagon.
     */
    public static enum Vertex {
        N, NE, NW, S, SE, SW;

        public static Collection<Vertex> getAdjacentVertices(final Vertex iVertex)
        {
            List<Vertex> vertices = new ArrayList<>();

            switch (iVertex)
            {
                case N:
                    vertices.add(NE);
                    vertices.add(NW);
                    break;
                case NE:
                    vertices.add(N);
                    vertices.add(SE);
                    break;
                case NW:
                    vertices.add(SW);
                    vertices.add(N);
                    break;
                case S:
                    vertices.add(SW);
                    vertices.add(SE);
                    break;
                case SE:
                    vertices.add(S);
                    vertices.add(NE);
                    break;
                case SW:
                    vertices.add(S);
                    vertices.add(NW);
                    break;
                default:
                    break;
            }

            return vertices;
        }
    }

    /**
     * Represents an edge on a 'pointy-top' hexagon
     */
    public static enum Edge {
        NE, E, SE, SW, W, NW;

        public static Collection<Edge> getAdjacentEdges(final Edge iEdge)
        {
            List<Edge> edges = new ArrayList<>();

            switch(iEdge)
            {
                case NE:
                    edges.add(E);
                    edges.add(NW);
                    break;
                case E:
                    edges.add(NE);
                    edges.add(SE);
                    break;
                case SE:
                    edges.add(E);
                    edges.add(SW);
                    break;
                case SW:
                    edges.add(W);
                    edges.add(SE);
                    break;
                case W:
                    edges.add(SW);
                    edges.add(NW);
                    break;
                case NW:
                    edges.add(W);
                    edges.add(NE);
                    break;
                default:
                    break;
            }
            return edges;
        }

        public static Collection<Vertex> getAdjacentVertices(final Edge iEdge) {
            List<Vertex> vertices = new ArrayList<>();

            switch(iEdge)
            {
                case NE:
                    vertices.add(Vertex.N);
                    vertices.add(Vertex.NE);
                    break;
                case E:
                    vertices.add(Vertex.NE);
                    vertices.add(Vertex.SE);
                    break;
                case SE:
                    vertices.add(Vertex.SE);
                    vertices.add(Vertex.S);
                    break;
                case SW:
                    vertices.add(Vertex.S);
                    vertices.add(Vertex.SW);
                    break;
                case W:
                    vertices.add(Vertex.SW);
                    vertices.add(Vertex.NW);
                    break;
                case NW:
                    vertices.add(Vertex.NW);
                    vertices.add(Vertex.N);
                    break;
                default:
                    break;
            }

            return vertices;
        }
    }
}
